use super::*;

pub(crate) fn expand(attr: &EnumType, input: &DeriveInput) -> TokenStream {
	let body = match &input.data {
		Data::Struct(data) => impl_struct(attr, input, data),
		Data::Enum(data) => impl_enum(attr, input, data),
		Data::Union(_) => Err(Error::new_spanned(input, "union are not supported")),
	};
	let body = match body {
		Ok(body) => body,
		Err(error) => return error.to_compile_error(),
	};
	let ty = &input.ident;

	let mut generics = input.generics.clone();
	generics.params.insert(
		0,
		GenericParam::Lifetime(LifetimeParam::new(Lifetime::new("'__napih_from", Span::call_site()))),
	);
	let (impl_generics, _, _) = generics.split_for_impl();
	let (_, ty_generics, where_clause) = input.generics.split_for_impl();
	let ref_type = match attr {
		EnumType::Ref | EnumType::RefExt => quote! { &'__napih_from },
		EnumType::Mut | EnumType::MutExt => quote! { &'__napih_from mut },
		_ => quote! {},
	};
	quote! {
		#[allow(unused_qualifications)]
		#[automatically_derived]
		impl #impl_generics ::napih::FromNapi<'__napih_from> for #ref_type #ty #ty_generics #where_clause {
			fn from_napi(v: ::napih::Value<'__napih_from>) -> ::napih::Result<'static, Self> {
				#body
			}
		}
	}
}
fn impl_struct(attr: &EnumType, input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> {
	let ty = &input.ident;
	match attr {
		EnumType::Num | EnumType::Str => return Err(Error::new(Span::call_site(), "unsupport")),
		EnumType::Obj => match &data.fields {
			Fields::Named(FieldsNamed { named: fields, .. }) => {
				if fields.iter().any(|field| !matches!(field.vis, Visibility::Public(_))) {
					return Err(Error::new_spanned(fields, "private fields are not supported for IntoNapi"));
				}
				let field_conversions = fields.iter().map(|field| {
					let ident = field.ident.as_ref().unwrap();
					let name = match ident.to_string().lit_cstr() {
						Ok(v) => v,
						Err(e) => return e,
					};
					let ty = &field.ty;

					quote! {
						#ident: {
							let field_value = v.get_named(#name)?;
							<#ty as ::napih::FromNapi<'__napih_from>>::from_napi(field_value)?
						}
					}
				});
				Ok(quote! {
					let _ = v.is_object()?;
					Ok(#ty {
						#(#field_conversions),*
					})
				})
			}
			Fields::Unit => Ok(quote! {
				let _ = v.is_object()?;
				Ok(#ty {})
			}),
			_ => return Err(Error::new_spanned(input, "tuple structs are supported for object conversion")),
		},
		EnumType::Ref => Ok(quote! {
			unsafe{v.ref_wrap()}
		}),
		EnumType::Mut => Ok(quote! {
			unsafe{v.mut_wrap()}
		}),
		EnumType::RefExt => Ok(quote! {
			unsafe{v.ref_external()}
		}),
		EnumType::MutExt => Ok(quote! {
			unsafe{v.mut_external()}
		}),
	}
}
fn impl_enum(attr: &EnumType, input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> {
	let ty = &input.ident;
	match attr {
		EnumType::Num => {
			let mut items = vec![];
			for var in data.variants.iter() {
				let name = &var.ident;
				match &var.discriminant {
					Some((_, Expr::Lit(ExprLit { lit: Lit::Int(v), .. }))) => items.push(quote! { #v => Ok(#ty::#name) }),
					_ => return Err(Error::new_spanned(var, "enum variant must have discriminant")),
				}
			}
			Ok(quote! {
				let _v = u32::from_napi(v)?;
				match _v {
					#(#items,)*
					_=>Err(::napih::Status::invalid_arg(stringify!(arg number can not convert to enum #ty)))
				}
			})
		}
		EnumType::Str => {
			let mut items = vec![];
			for var in data.variants.iter() {
				let name = &var.ident;
				let value = LitStr::new(&name.to_string(), Span::call_site());
				items.push(quote! { #value => Ok(#ty::#name) });
			}
			Ok(quote! {
				let _v = String::from_napi(v)?;
				let _v:&str = _v.as_str();
				match _v {
					#(#items,)*
					_=>Err(::napih::Status::invalid_arg(stringify!(arg string can not convert to enum #ty)))
				}
			})
		}
		EnumType::Obj => return Err(Error::new_spanned(input, "unsupport")),
		EnumType::Ref => Ok(quote! {
			unsafe{v.ref_wrap()}
		}),
		EnumType::Mut => Ok(quote! {
			unsafe{v.mut_wrap()}
		}),
		EnumType::RefExt => Ok(quote! {
			unsafe{v.ref_external()}
		}),
		EnumType::MutExt => Ok(quote! {
			unsafe{v.mut_external()}
		}),
	}
}
